#include "baseDB.h"
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <assert.h>

vector<unsigned int> V_CHANGEs; // for recording cell's update
vector<C_CELL*> V_CELLs; // netlist cell
vector<unsigned int> V_IOs;
vector< vector< vector<int> > > THRD_GRID;
vector< vector< vector<int> > > THRD_OCC_GRID;
vector< vector<int> > TDM_LOC; // 2D map for recording tdm locations

unsigned int XDIM=0,YDIM=0,ZDIM=0;
unsigned int XFREQ=0,YFREQ=0,TDMDIST=0;

C_CELL::C_CELL(const char* instname,E_CELL_TYPE type) {
    
    _name.assign(instname);
    _x = 0.0;
    _y = 0.0;
    _z = 0.0;
    _type = type;
    _isFF = false;
    _fanout.clear();
}

C_CELL::C_CELL(const char* instname) {
    
    _name.assign(instname);
    _x = 0.0;
    _y = 0.0;
    _z = 0.0;
    _type = e_clb;
    _isFF = false;
    _fanout.clear();
}

C_CELL::~C_CELL() {
    
}
void C_CELL::setType(E_CELL_TYPE type) { _type = type;}
E_CELL_TYPE C_CELL::getType(void) { return _type;}
void C_CELL::setFF(bool isFF) { _isFF = isFF;}
bool C_CELL::isFF(void) { return _isFF;}

void C_CELL::setLoc(int x,int y,int z){
    _x = x;
    _y = y;
    _z = z;

    _fx = (float)x;
    _fy = (float)y;
    _fz = (float)z;

}
void C_CELL::setLoc(float x,float y,float z){
    _fx = x;
    _fy = y;
    _fz = z;

    _x = (int)x;
    _y = (int)y;
    _z = (int)z;
}

void C_CELL::getLoc(int& x,int& y,int& z) {
    x = _x;
    y = _y;
    z = _z;
}
void C_CELL::getLoc(float& x,float& y,float& z) {
    x = _fx;
    y = _fy;
    z = _fz;
}

const char* C_CELL::getName(void) { return _name.data(); }

void C_CELL::addFanout(unsigned int cellIdx) {
    _fanout.push_back(cellIdx);
}

void C_CELL::addFanin(unsigned int cellIdx) {
    _fanin.push_back(cellIdx);
}

bool C_CELL::getFanout(vector<unsigned int>& cellIdxs) {
    if (_fanout.size() == 0)
        return false;
    else
        cellIdxs = _fanout;
    
    return true;
}

bool C_CELL::getFanin(vector<unsigned int>& cellIdxs) {
    if (_fanin.size() == 0)
        return false;
    else
        cellIdxs = _fanin;
    
    return true;
}

void init_3d_grid(unsigned int xdim, unsigned int ydim, unsigned int zdim) {

    XDIM = xdim;
    YDIM = ydim;
    ZDIM = zdim;

    V_CHANGEs.clear();
    THRD_GRID.resize(XDIM+2);

    for (unsigned int i=0;i<(XDIM+2);i++) {
        
        THRD_GRID[i].resize(YDIM+2);

        for (unsigned int j=0;j<(YDIM+2);j++) {
            THRD_GRID[i][j].resize(ZDIM);
        }
    }

    // initialize 3D array value
    for (unsigned i=0;i<(XDIM+2);i++) {
        for (unsigned j=0;j<(YDIM+2);j++) {
            for (unsigned k=0;k<ZDIM;k++) {
                THRD_GRID[i][j][k] = -1; // initialize grid
            }
        }
    }
    //
}

void add_cell(const char* clbName,E_CELL_TYPE type) {
    
    C_CELL* tmpCLB = new C_CELL(clbName,type);
    
    int idx = V_CELLs.size();
    V_CELLs.push_back(tmpCLB);
    V_IOs.push_back(idx);
}

C_CELL* get_cell_ptr(const char* cellName) {
    
    string name;
    name.assign(cellName);

    for (int i=0;i<V_CELLs.size();i++) {
        
        if (name.compare(V_CELLs[i]->getName()) == 0)
            return V_CELLs[i];
    }
    
    //for (int i=0;i<V_IOs.size();i++) {
    //    
    //    if (name.compare(V_IOs[i]->getName()) == 0)
    //        return V_IOs[i];
    //}

    return NULL;
    
}

unsigned int get_cell_idx(const char* cellName) {
    
    string name;
    name.assign(cellName);

    for (unsigned int i=0;i<V_CELLs.size();i++) {
        
        if (name.compare(V_CELLs[i]->getName()) == 0)
            return i;
    }
    
    //for (int i=0;i<V_IOs.size();i++) {
    //    
    //    if (name.compare(V_IOs[i]->getName()) == 0)
    //        return V_IOs[i];
    //}

    return -1;
}

bool set_cell_loc(const char* cellName,int x,int y,int z) {
    
    C_CELL* pCell = get_cell_ptr(cellName);
    int idx = get_cell_idx(cellName);

    THRD_GRID[x][y][z] = idx;
    
    if (pCell != NULL)
        pCell->setLoc(x,y,z);
    else
        return false;

    return true;
}

void place_cell_randomly(void) {

    printf("place_cell_randomly...\n");
    unsigned int dice_clb_vec_size = XDIM*YDIM*ZDIM;
    unsigned int dice_io_vec_size = (XDIM+YDIM)*2*ZDIM;
    int x,y,z;
    int cellIdx;

    V_CHANGEs.clear();

    vector<int> dice_vec;
    dice_vec.resize(dice_clb_vec_size); // idx -> clb loc, val -> cell idx
    
    // RANDOMLY PLACE CLB
    // init vector
    for (int i=0;i<dice_clb_vec_size;i++)
        dice_vec[i] = i;

    random_shuffle (dice_vec.begin(),dice_vec.end());

    //for (int i=0;i<dice_clb_vec_size;i++)
    //    printf("%d ",dice_vec[i]);
    
    //printf("\n");

    // assign loc to clb
    //
    //   15 16 17
    //   12 13 14
    //Z1  9 10 11 
    //    6  7  8
    //    3  4  5
    //Z0  0  1  2
    for (int i=0;i<dice_clb_vec_size;i++) {
        
        cellIdx = dice_vec[i];
        
        if (cellIdx>=V_CELLs.size())
            continue; // the loc does not belong to any cell

        x = i%XDIM+1;
        y = (i/XDIM)%YDIM+1;
        z = i/(XDIM*YDIM);

        V_CELLs[cellIdx]->setLoc(x,y,z);
        printf("Place %s (%d,%d,%d)\n",V_CELLs[cellIdx]->getName(),x,y,z);
        V_CHANGEs.push_back(cellIdx);

    }

    // RANDOMLY PLACE IO
    // init vector
    dice_vec.resize(dice_io_vec_size);
    for (int i=0;i<dice_io_vec_size;i++)
        dice_vec[i] = i;

    random_shuffle (dice_vec.begin(),dice_vec.end());

    // assign loc to io
    //    18  19  20
    //  23           17
    //  22           16
    //  21           15
    //Z1  12  13  14
    //     6   7   8          
    //  11            5
    //  10            4
    //   9            3 <-- vertical
    //Z0   0   1   2  <-- horizontal
    int planeIdx;
    int tmp;
    for (int i=0;i<dice_io_vec_size;i++) {
        
        cellIdx = dice_vec[i];
        
        if (cellIdx>=V_IOs.size())
            continue; // the loc does not belong to any IO
        //printf("i=%d cellIdx=%d\n",i,cellIdx);
        z = i/((XDIM+YDIM)*2);
        planeIdx = i%((XDIM+YDIM)*2);
        assert(XDIM==YDIM);
        
        tmp = planeIdx/XDIM;

        if ( (planeIdx/XDIM)%2 == 0 ) { // horizontal
            
            x = planeIdx%XDIM;

            if ( (planeIdx/XDIM) == 0)
                y = 0; // SOUTH
            else
                y = YDIM+1; // NORTH

        } else { // vertical
            
            y = planeIdx%XDIM;

            if ( (planeIdx/XDIM) == 1)
                x = XDIM+1; // EAST
            else
                x = 0; // WEST
            
        }
        //if (planeIdx/XDIM)
        //x = planeIdx
        //y = ;

        V_CELLs[V_IOs[cellIdx]]->setLoc(x,y,z);
        printf("Place IO %s (%d,%d,%d)\n",V_CELLs[V_IOs[cellIdx]]->getName(),x,y,z);
        V_CHANGEs.push_back(V_IOs[cellIdx]);
    }

}    

void placement_statistics(float& estWL,int& interLayerWidth) {
    estWL = _calc_SemiPerimeter(1.0);
    interLayerWidth = _count_interlayer_bandwidth();
}

void print_network(void) {
    
    vector<unsigned int> v_cells;
    unsigned int myidx;

    // Start from IOs
    /*for (int i;i<V_IOs.size();i++) {
        v_cells.clear();
        
        if (V_IOs[i]->getType() == e_ioin) {
            
            printf("INPUT: %s\n",V_IOs[i]->getName());
            
            V_IOs[i]->getFanout(v_cells);
            for (int j=0;j<v_cells.size();j++) {
                printf("    %s\n",v_cells[j]->getName());
            }

        } else
            printf("OUTPUT: %s\n",V_IOs[i]->getName());
    }*/

    // Continue to CELL
    for (int i;i<V_CELLs.size();i++) {
        
        v_cells.clear();

        if (V_CELLs[i]->getType() == e_clb)
            printf ("CELL: %s\n",V_CELLs[i]->getName());
        else if (V_CELLs[i]->getType() == e_ioin)
            printf ("INPUT: %s\n",V_CELLs[i]->getName());
        else if (V_CELLs[i]->getType() == e_ioout)
            printf ("OUTPUT: %s\n",V_CELLs[i]->getName());

        V_CELLs[i]->getFanout(v_cells);
        for (int j=0;j<v_cells.size();j++) {
            myidx = v_cells[j];
            printf("    %s\n",V_CELLs[myidx]->getName());
        }
    }    
}

// private utilities
int _count_interlayer_bandwidth(void) {
    
    int count = 0;
    vector<int> rec;
    vector<unsigned int> fanouts;
    int x,y,z;
    int driver_z;
    unsigned int myidx;

    // check z dimension legalization
    for (unsigned int i=0;i<V_CELLs.size();i++) {
        V_CELLs[i]->getLoc(x,y,driver_z);
        
        if (driver_z>=ZDIM) {
            printf("WARNING: z dimension execeeds ZDIM\n");
            return 0;
        }
    }
    
    for (unsigned int i=0;i<V_CELLs.size();i++) {
        
        if (V_CELLs[i]->getType() == e_ioout)
            continue;
        
        rec.clear();
        rec.resize(ZDIM,0);
        //for (unsigned int j=0;j<rec.size();j++)
        //    printf("%d ",rec[j]);
        
        //printf(": %d\n",count);
        
        fanouts.clear();
        
        V_CELLs[i]->getFanout(fanouts);
        V_CELLs[i]->getLoc(x,y,driver_z);

        for (unsigned int j=0;j<fanouts.size();j++) {
            myidx = fanouts[j];
            V_CELLs[myidx]->getLoc(x,y,z);
            //printf("load z: %d\n",z);
            rec[z] = 1;
        }

        // remove count of driver's layer
        //printf("%d\n",driver_z);
        rec[driver_z] = 0;

        // count bandwidth
        for (int j=0;j<rec.size();j++) {
            if (rec[j] == 1) {
                count += abs(driver_z-j);
            }
        }
    }
    return count;
    //printf("WARNING: IO bandwidth is not implemented yet\n");
    //return count;

    /*for (unsigned int i=0;i<V_IOs.size();i++) {
        
        rec.resize(ZDIM,0);
        fanouts.clear();
        
        V_IOs[i]->getFanout(fanouts);
        V_IOs[i]->getLoc(x,y,driver_z);

        for (unsigned int j=0;j<fanouts.size();j++) {
            fanouts[j]->getLoc(x,y,z);
            rec[z] = 1;
        }

        // remove count of driver's layer
        rec[driver_z] = 0;

        // count bandwidth
        for (unsigned int j=0;j<rec.size();j++) {
            if (rec[j] == 1)
                count++;
        }
    }

    return count;*/
}

float _calc_SemiPerimeter(float verWght) {
    
    float WL = 0.0;

    vector<unsigned int> fanouts;
    int x,y,z;
    int MX,MY,MZ;
    int mx,my,mz;
    int driver_z;
    unsigned int myidx;

    for (unsigned int i=0;i<V_CELLs.size();i++) {
        
        if (V_CELLs[i]->getType() == e_ioout)
            continue;

        fanouts.clear();
        
        V_CELLs[i]->getFanout(fanouts);
        V_CELLs[i]->getLoc(MX,MY,MZ);
        
        // initialize boundary at driver
        mx = MX;
        my = MY;
        mz = MZ;

        for (unsigned int j=0;j<fanouts.size();j++) {
            myidx = fanouts[j];
            V_CELLs[myidx]->getLoc(x,y,z);
            
            // set upper bound
            if (x>MX)
                MX = x;
            if (y>MY)
                MY = y;
            if (z>MZ)
                MZ = z;
            // set lower bound
            if (x<mx)
                mx = x;
            if (y>my)
                my = y;
            if (z>mz)
                mz = z;

        }

        // calc estimate wirelength
        WL = WL + (float)(MX-mx) + (float)(MY-my) + verWght*(MZ-mz);
    }

    /*for (unsigned int i=0;i<V_IOs.size();i++) {
        
        fanouts.clear();
        
        V_IOs[i]->getFanout(fanouts);
        V_IOs[i]->getLoc(MX,MY,MZ);
        
        // initialize boundary at driver
        mx = MX;
        my = MY;
        mz = MZ;

        for (unsigned int j=0;j<fanouts.size();j++) {
            
            fanouts[j]->getLoc(x,y,z);
            
            // set upper bound
            if (x>MX)
                MX = x;
            if (y>MY)
                MY = y;
            if (z>MZ)
                MZ = z;
            // set lower bound
            if (x<mx)
                mx = x;
            if (y>my)
                my = y;
            if (z>mz)
                mz = z;

        }

        // calc estimate wirelength
        WL = WL + (float)(MX-mx) + (float)(MY-my) + verWght*(MZ-mz);
    }*/

    return WL;
}
